package com.alaia.alaiaauth.config;

import com.alaia.alaiaauth.details.MyWebAuthenticationDetailsSource;
import com.alaia.alaiaauth.filter.LoginFilter;
import com.alaia.alaiaauth.provider.MyAuthenticationProvider;
import com.alaia.alaiaauth.service.impl.UserDetailsServiceImpl;
import com.alaia.alaiacommoncore.auth.encoding.MD5PasswordEncoder;
import com.alaia.alaiacommoncore.auth.props.PermitUrlProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.Arrays;


@Configuration
@EnableConfigurationProperties(PermitUrlProperties.class)
@EnableRedisHttpSession
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private PermitUrlProperties permitUrlProperties ;

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

    @Autowired
    private LogoutSuccessHandler logoutSuccessHandler;

    @Autowired
    private LoginFilter loginFilter;

    @Autowired
    private MyWebAuthenticationDetailsSource myWebAuthenticationDetailsSource;


    @Autowired
    private SessionRegistryImpl sessionRegistry;


//    @Bean
//    @Override
//    public AuthenticationManager authenticationManagerBean() throws Exception {
//        return super.authenticationManagerBean();
//    }

    @Bean
    HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

    //自定义认证处理器
    @Bean
    MyAuthenticationProvider myAuthenticationProvider() {
        MyAuthenticationProvider myAuthenticationProvider = new MyAuthenticationProvider();
        myAuthenticationProvider.setPasswordEncoder(new MD5PasswordEncoder());
        myAuthenticationProvider.setUserDetailsService(userDetailsService);
        return myAuthenticationProvider;
    }

    @Override
    @Bean
    protected AuthenticationManager authenticationManager() throws Exception {
        ProviderManager manager = new ProviderManager(Arrays.asList(myAuthenticationProvider()));
        return manager;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable();
        //使用添加Filter的方法自定义相关认证逻辑，但是也有缺点，每一个请求都会经过这个Filter，所以我们的认证逻辑可以自定义
        //之前我们通过自定义过滤器，将自定义的过滤器加入到 Spring Security 过滤器链中，进而实现了添加登录验证码功能，
        //但是我们也说这种方式是有弊端的，就是破坏了原有的过滤器链，请求每次都要走一遍验证码过滤器，这样不合理。
        //重写用户名密码的登录认证过滤器
        http.addFilterAt(loginFilter, UsernamePasswordAuthenticationFilter.class);
        //session 处理还有一个关键的过滤器叫做 ConcurrentSessionFilter，本来这个过滤器是不需要我们管的，
        //但是这个过滤器中也用到了 SessionRegistryImpl，而 SessionRegistryImpl 现在是由我们自己来定义的
        //第一个参数：sessionRegistry 就是我们前面提供的 SessionRegistryImpl 实例。
        //第二个参数：是一个处理 session 过期后的回调函数，也就是说，当用户被另外一个登录踢下线之后，你要给什么样的下线提示，就在这里来完成。
        http.addFilterAt(new ConcurrentSessionFilter(sessionRegistry, event -> {
            HttpServletResponse resp = event.getResponse();
            resp.setContentType("application/json;charset=utf-8");
            resp.setStatus(401);
            PrintWriter out = resp.getWriter();
            out.write(new ObjectMapper().writeValueAsString("您已在另一台设备登录，本次登录已下线!"));
            out.flush();
            out.close();
        }), ConcurrentSessionFilter.class);
        http.authorizeRequests()
                // 放行接口
                .antMatchers(permitUrlProperties.getIgnored()).permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                // 异常处理(权限拒绝、登录失效等)
                .and().exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)//匿名用户访问无权限资源时的异常处理
                .accessDeniedHandler(accessDeniedHandler)//登录用户没有权限访问资源
                // 登入
                //注意：这里我重写了：WebAuthenticationDetails 是为了在getDetails时有扩展字段可以使用
                .and().formLogin().authenticationDetailsSource(myWebAuthenticationDetailsSource).permitAll()//允许所有用户
                .successHandler(authenticationSuccessHandler)//登录成功处理逻辑
                .failureHandler(authenticationFailureHandler)//登录失败处理逻辑
                // 登出
                .and().logout().permitAll()//允许所有用户
                .logoutSuccessHandler(logoutSuccessHandler)//登出成功处理逻辑
                // 会话管理（这里做了自定义处理），所以全部屏蔽掉
//                .and().sessionManagement().invalidSessionStrategy(invalidSessionStrategy) // 超时处理
//                .maximumSessions(1)//同一账号同时登录最大用户数
//                .expiredSessionStrategy(sessionInformationExpiredHandler) // 顶号处理
        ;
    }
}
